package org.example.domain.strategy.service.armory;

import org.example.domain.strategy.model.Raffle.StrategyAwardEntity;
import org.example.domain.strategy.model.Raffle.StrategyEntity;
import org.example.domain.strategy.model.Raffle.StrategyRuleEntity;
import org.example.domain.strategy.repository.IStrategyRepository;

import org.example.types.common.Constants;
import org.example.types.exception.AppException;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.security.SecureRandom;
import java.util.*;


@Service
public class StrategyArmoryDispatch implements IStrategyArmory,IStrategyDispatch{

    @Resource
    private IStrategyRepository strategyRepository;


    /**
     * 根据策略id装配 相关抽奖表, 奖品库存 到redis
     * @param strategyId 策略id
     * @return
     */
    @Override
    public boolean assembleLotteryStrategy(Long strategyId) {
        //1.从仓储层取到包含对应信息的的entity类型
         List<StrategyAwardEntity> strategyAwardEntities = strategyRepository.queryStrategyAwardList(strategyId);
        //2.把没有权重的奖品策略配置到redis
        assembleLotteryStrategy(String.valueOf(strategyId),strategyAwardEntities);

        //将奖品的库存存到redis中
        assembleLotteryStockToRedis(strategyAwardEntities);

        //3.从数据库中拿出strategy表对应的strategyEntity字段
        StrategyEntity strategyEntity = strategyRepository.queryStrategyEntityByStrategyId(strategyId);

        //4.判断策略实体中的rule-model字段是否包含rule_weight,不包含直接返回,包含则进行之后逻辑
        String ruleWeight = strategyEntity.getRuleWeight();
        if (ruleWeight==null) return true;

        //5.取出对应策略id的strategyRule表对应的实体
        StrategyRuleEntity strategyRuleEntity = strategyRepository.queryStrategyRuleEntityByStrategyIdAndRuleModel(strategyId,ruleWeight);

        //6.判断是否为空,为空则说明表strategy_rule不包含ruleModel字段为rule_weight的信息
        if (strategyRuleEntity == null )
            throw new AppException("strategy_rule","strategy_rule中不包含ruleModel字段为rule_weight的信息");


        //7.取出实体中的rule_weight对应的rule_value值,弄成一个字符串数组
        Map<String, List<Integer>> ruleValuesMap = strategyRuleEntity.getRuleValuesMap();
        Set<String> ruleValueMapsKeys = ruleValuesMap.keySet();
        List<String> keys = new ArrayList<>(ruleValueMapsKeys);

        //8.分别把对应map的策略抽奖表存到redis中
        for (int i =0;i<ruleValuesMap.size();i++){
            List<Integer> ruleValues  = ruleValuesMap.get(keys.get(i));
            List<StrategyAwardEntity> strategyAwardEntitiesClone = new ArrayList<>(strategyAwardEntities);
            strategyAwardEntitiesClone.removeIf(entity -> !ruleValues.contains(entity.getAwardId()));
            assembleLotteryStrategy(String.valueOf(strategyId).concat("_").concat(String.valueOf(keys.get(i))),strategyAwardEntitiesClone);
        }


        return true;
    }

    /**
     * 根据活动id装配策略
     * @param activityId 活动id
     * @return
     */
    @Override
    public boolean assembleLotteryStrategyByActivityId(Long activityId) {
        Long strategyId = strategyRepository.queryStrategyIdByActivityId(activityId);
        return assembleLotteryStrategy(strategyId);
    }


    /**
     * 根据策略奖品实体列表装配奖品库存至redis
     * @param strategyAwardEntities 策略奖品实体列表
     */
    private void assembleLotteryStockToRedis(List<StrategyAwardEntity> strategyAwardEntities) {
        for (StrategyAwardEntity strategyAwardEntity :strategyAwardEntities){
            String awardId = String.valueOf(strategyAwardEntity.getAwardId());
            String strategyId = String.valueOf(strategyAwardEntity.getStrategyId());
            String cacheKey = Constants.RedisKey.STRATEGY_AWARD_COUNT_KEY + strategyId + Constants.UNDERLINE + awardId;
            Integer awardCount = strategyAwardEntity.getAwardCount();
            strategyRepository.cacheStrategyAwardCount(cacheKey, Long.valueOf(awardCount));
        }

    }

    /**
     *  把 key 当作键 , 构建策略奖品列表的抽奖表作为值存入redis
     * @param key redis的键
     * @param strategyAwardEntities 策略奖品列表
     * @return
     */
    public boolean assembleLotteryStrategy(String key,List<StrategyAwardEntity> strategyAwardEntities) {

        //1.根据这个实体的概率值来计算出应该放在redis中的award的个数
            //(1)算相加的总概率
        BigDecimal totalRate = strategyAwardEntities.stream()
                .map(StrategyAwardEntity::getAwardRate)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
            //(2)计算其中最小的概率
        BigDecimal minRate = strategyAwardEntities.stream()
                .map(StrategyAwardEntity::getAwardRate)
                .min(BigDecimal::compareTo)
                .orElse(BigDecimal.ZERO);
            //(3)算出以最小概率为粒度的个数范围,即是用总概率除以最小概率,再用各个概率乘以这个个数范围就可以得到应该存在redis的各个概率对应的奖品个数了
        BigDecimal rateRange = totalRate.divide(minRate,0, RoundingMode.CEILING);

        //2.将各个概率乘以这个范围得到应该存入到redis的个数,再得到奖品id查找表
        List<Integer> strategyAwardSearchRateTables = new ArrayList<>(rateRange.intValue());

        for(StrategyAwardEntity strategyAwardEntity:strategyAwardEntities){
            int awardId = strategyAwardEntity.getAwardId();
            BigDecimal awardRate = strategyAwardEntity.getAwardRate();
            for(int i= 0;i<awardRate.multiply(rateRange).setScale(0,RoundingMode.CEILING).intValue();i++) {
                strategyAwardSearchRateTables.add(awardId);
            }
        }

        //3.打乱概率查找表,List可以打乱
        Collections.shuffle(strategyAwardSearchRateTables);


        //4.生成查找表的map形式,以便于存储到redis中
        Map<Integer,Integer> strategyAwardSearchRateTablesMap = new HashMap<>();
        for (int i=0;i<strategyAwardSearchRateTables.size();i++){
            strategyAwardSearchRateTablesMap.put(i,strategyAwardSearchRateTables.get(i));
        }

        //5.调用仓储层把各个概率对应的award数量存入redis中,把对应的map的大小也要存入,因为别人要用这个来进行随机数的选取
        strategyRepository.storeStrategyAwardSearchRateTable(key,strategyAwardSearchRateTables.size(),strategyAwardSearchRateTablesMap);

        return true;
    }


    @Override
    public Integer getRandomAwardId(Long strategyId) {
        //1.先查出对应strategyId对应的rateRange,因为这个是随机数的范围值
        Integer rateRange = strategyRepository.getRateRangeByStrategyId(strategyId);
        //2.根据这个范围值求出随机值
        Integer randomIndex =new SecureRandom().nextInt(rateRange);

        //3.根据随机值在redis中取对应的awardId,
        return strategyRepository.getAwardIdByRandomIndex(strategyId,randomIndex);
    }

    /**
     * 根据策略id 和 权重信息 从redis中随机获取奖品id
     * 策略id 和 权重信息 组成 相应权重对应的抽奖表的 key
     * @param strategyId 策略id
     * @param ruleWeightValue 权重值
     * @return 奖品id
     */
    @Override
    public Integer getRandomAwardId(Long strategyId, String ruleWeightValue) {
        String key = String.valueOf(strategyId).concat("_").concat(ruleWeightValue);
        // 分布式部署下，不一定为当前应用做的策略装配。也就是值不一定会保存到本应用，而是分布式应用，所以需要从 Redis 中获取。
        Integer rateRange = strategyRepository.getRateRange(key);
        // 通过生成的随机值，获取概率值奖品查找表的结果
        return strategyRepository.getAwardId(key, new SecureRandom().nextInt(rateRange));
    }

    @Override
    public Boolean subtractionAwardStock(Long strategyId, Long awardId) {
        String key = Constants.RedisKey.STRATEGY_AWARD_COUNT_KEY + strategyId + Constants.UNDERLINE + awardId;
        return strategyRepository.subtractionAwardStockInRedis(key);

    }

    @Override
    public boolean subtractionAwardStock(Long strategyId, Long awardId, Date endDateTime) {
        String key = Constants.RedisKey.STRATEGY_AWARD_COUNT_KEY + strategyId + Constants.UNDERLINE + awardId;
        return strategyRepository.subtractionAwardStockInRedis(key,endDateTime);
    }

    @Override
    public List<StrategyAwardEntity> getAwardEntityList(Long strategyId) {
        List<StrategyAwardEntity> raffleAwardEntities = strategyRepository.queryStrategyAwardList(strategyId);

        return raffleAwardEntities;
    }

    @Override
    public List<StrategyAwardEntity> getAwardEntityListByActivityId(Long activityId) {
        Long strategyId = strategyRepository.queryStrategyIdByActivityId(activityId);
        List<StrategyAwardEntity> raffleAwardEntities = strategyRepository.queryStrategyAwardList(strategyId);
        return raffleAwardEntities;
    }


}
